package yolo

import (
	"appengine"
	"appengine/datastore"
	"appengine/memcache"
	"appengine/taskqueue"
	"fmt"
	"net/http"
	"net/mail"
	"strings"
	"strconv"
	"time"
)

func init() {
	http.HandleFunc("/admin/", showAdminTop)

	// Proof of concept, no fancy UI
	http.HandleFunc("/admin/users", listUsers)
	http.HandleFunc("/admin/user", showUser)
	http.HandleFunc("/admin/issues", listIssues)
	http.HandleFunc("/admin/issue", showIssue)
	http.HandleFunc("/admin/activities", listActivities)
	http.HandleFunc("/admin/timeline", listTimeline)

	// Datastore
	http.HandleFunc("/admin/purge_datastore", handlePurgeDatastore)
	http.HandleFunc("/admin/purge_datastore_ymd", handlePurgeDatastoreYMD)
	http.HandleFunc("/worker/purge_datastore", purgeDatastore)

	// Memcache
	http.HandleFunc("/admin/memcache_delete", handleDeleteMemcache)
	http.HandleFunc("/admin/memcache_get_counter", handleGetMemcacheCounter)
	http.HandleFunc("/admin/memcache_set_counter", handleSetMemcacheCounter)

	// Pollers
	http.HandleFunc("/admin/add_poll", handleAddPollContext)
	http.HandleFunc("/admin/start_poll", handleStartPollContext)
	http.HandleFunc("/admin/stop_poll", handleStopPollContext)
	http.HandleFunc("/admin/delete_poll", handleDeletePollContext)

	http.HandleFunc("/admin/start_poll_all", handleStartAllPollContext)
	http.HandleFunc("/admin/stop_poll_all", handleStopAllPollContext)
	http.HandleFunc("/admin/delete_poll_all", handleDeleteAllPollContext)

	// Takes issue and siteurl
	http.HandleFunc("/admin/update_issue", handleUpdateIssue)

	// Receiving mails
	http.HandleFunc("/_ah/mail/", handleIncomingMail)
}

func showAdminTop(w http.ResponseWriter, r *http.Request) {
	c := appengine.NewContext(r)
	var contexts []PollContext
	keys, err := GetPollContexts(c, &contexts)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	sites := GetSupportedSites()
	NewAdminTopPage(w, r, keys, contexts, sites).Render()
}

func listUsers(w http.ResponseWriter, r *http.Request) {
	c := appengine.NewContext(r)
	offset := getFormValueInt(r, "offset", 0)
	limit := getFormValueInt(r, "limit", 30)
	var users []User
	err := GetUsers(c, offset, limit, &users)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	NewAdminUsersPage(w, r, users, offset, limit).Render()
}

func showUser(w http.ResponseWriter, r *http.Request) {
	c := appengine.NewContext(r)
	email := r.FormValue("email")
	u := NewUser(email)
	commits, err := u.GetFileCommitCounts(c)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	users, err := GetUserActivityCounts(c, email)
	NewAdminUserPage(w, r, email, commits, users).Render()
}

func listIssues(w http.ResponseWriter, r *http.Request) {
	c := appengine.NewContext(r)
	offset := getFormValueInt(r, "offset", 0)
	limit := getFormValueInt(r, "limit", 20)
	var issues []Issue
	err := GetIssues(c, r.FormValue("email"), offset, limit, &issues)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	NewAdminIssuesPage(w, r, issues, offset, limit).Render()
}

func showIssue(w http.ResponseWriter, r *http.Request) {
	c := appengine.NewContext(r)
	id := r.FormValue("issue")
	if id == "" {
		http.Error(w, "issue must be specified.", http.StatusBadRequest)
		return
	}
	data, err := GetIssueDetailDataById(c, id)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	NewAdminIssuePage(w, r, data).Render()
}

func listActivities(w http.ResponseWriter, r *http.Request) {
	c := appengine.NewContext(r)
	offset := getFormValueInt(r, "offset", 0)
	limit := getFormValueInt(r, "limit", 20)
	activities, err := GetActivities(c, r.FormValue("email"), r.FormValue("issue"), offset, limit)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	NewAdminActivitiesPage(w, r, activities, offset, limit).Render()
}

func listTimeline(w http.ResponseWriter, r *http.Request) {
	c := appengine.NewContext(r)
	offset := getFormValueInt(r, "offset", 0)
	limit := getFormValueInt(r, "limit", 20)
	activities, err := GetTimeline(c, r.FormValue("email"), offset, limit)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	NewAdminActivitiesPage(w, r, activities, offset, limit).Render()
}

func handleStartAllPollContext(w http.ResponseWriter, r *http.Request) {
	c := appengine.NewContext(r)
	var contexts []PollContext
	keys, err := GetPollContexts(c, &contexts)
	for _, k := range keys {
		err = StartPollContext(c, k.Encode(), r.FormValue("start") != "")
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
		}
	}
	fmt.Fprintf(w, "Started %d poll contexts", len(keys))
}

func handleStopAllPollContext(w http.ResponseWriter, r *http.Request) {
	c := appengine.NewContext(r)
	var contexts []PollContext
	keys, err := GetPollContexts(c, &contexts)
	for _, k := range keys {
		err = StopPollContext(c, k.Encode())
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
		}
	}
	fmt.Fprintf(w, "Stopped %d poll contexts", len(keys))
}

func handleDeleteAllPollContext(w http.ResponseWriter, r *http.Request) {
	c := appengine.NewContext(r)
	var contexts []PollContext
	keys, err := GetPollContexts(c, &contexts)
	for _, k := range keys {
		err = DeletePollContext(c, k.Encode())
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
		}
	}
	fmt.Fprintf(w, "Deleted %d poll contexts", len(keys))
}

func handleAddPollContext(w http.ResponseWriter, r *http.Request) {
	c := appengine.NewContext(r)
	siteurl := r.FormValue("siteurl")

	if siteurl == "" {
		http.Error(w, "SiteURL needs to be specified", http.StatusBadRequest)
		return
	}

	if !HasPoller(siteurl) {
		http.Error(w, "Unknown site type:" + siteurl, http.StatusBadRequest)
		return
	}

	poll_mode := PollModeUnknown
	switch r.FormValue("poll_mode") {
	case "update":
		poll_mode = PollModeUpdate
	case "backdate":
		poll_mode = PollModeBackdate
	}
	if poll_mode == PollModeUnknown {
		http.Error(w, "Invalid poll mode", http.StatusBadRequest)
		return
	}

	var modified_before time.Time = time.Unix(0, 0)
	var modified_after time.Time= time.Unix(0, 0)
	var backdate_timerange time.Duration = -2 * time.Hour
	var err error

	switch poll_mode {
	case PollModeUpdate:
		after := r.FormValue("modified_after")
		if after != "" {
			modified_after, err = time.Parse("2006-01-02", after)
			if err != nil {
				http.Error(w, "Invalid date format", http.StatusBadRequest)
				return
			}
		}
	case PollModeBackdate:
		before := r.FormValue("modified_before")
		if before != "" {
			modified_before, err = time.Parse("2006-01-02", before)
			if err != nil {
				http.Error(w, "Invalid date format", http.StatusBadRequest)
				return
			}
		}
		timerange := r.FormValue("backdate_timerange")
		if timerange != "" {
			tr, err := strconv.Atoi(timerange)
			if err == nil {
				backdate_timerange = time.Duration(-tr) * time.Minute
			}
		}
		modified_after = modified_before.Add(backdate_timerange)
	}

	created := time.Now()
	c.Debugf("%s", modified_before)
	c.Debugf("%s", modified_after)
	c.Debugf("%s", created)
	c.Debugf("%d", backdate_timerange)
	var args = make([]string, 1)
	if siteurl == SiteGoogleCodesite {
		project := r.FormValue("project")
		if project == "" {
			http.Error(w, "project needs to be specified for codesite", http.StatusBadRequest)
			return
		}
		args[0] = project
	}

	pc := &PollContext{siteurl, args, created, modified_after, modified_before, poll_mode, false, 0, backdate_timerange}
	err = AddPollContext(c, pc)
	if err != nil {
		http.Error(w, "Could not add poll: "+err.Error(), http.StatusInternalServerError)
		return
	}

	fmt.Fprintf(w, "Added and started new poll context: %s, mode:%v, %v-%v", siteurl, poll_mode, modified_after, modified_before)
}

func handleStartPollContext(w http.ResponseWriter, r *http.Request) {
	c := appengine.NewContext(r)
	key := r.FormValue("key")
	err := StartPollContext(c, key, r.FormValue("start") != "")
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	fmt.Fprintf(w, "Posted start polling request for: %s", key)
}

func handleStopPollContext(w http.ResponseWriter, r *http.Request) {
	c := appengine.NewContext(r)
	key := r.FormValue("key")
	err := StopPollContext(c, key)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	fmt.Fprintf(w, "Marked stop polling for: %s", key)
}

func handleDeletePollContext(w http.ResponseWriter, r *http.Request) {
	c := appengine.NewContext(r)
	key := r.FormValue("key")
	err := DeletePollContext(c, key)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	fmt.Fprintf(w, "Deleted poll context: %s", key)
}

func handlePurgeDatastore(w http.ResponseWriter, r *http.Request) {
	c := appengine.NewContext(r)
	kinds := strings.Split(r.FormValue("kind"), ",")
	postedKinds := make([]string, 0)
	errorKinds := make([]string, 0)
	for _, v := range kinds {
		kind := strings.Trim(v, " ")
		q := datastore.NewQuery(kind).Limit(1).KeysOnly()
		keys, err := q.GetAll(c, nil)
		if err != nil && len(keys) == 1 {
			c.Warningf("Non-existent kind: %s", kind)
			errorKinds = append(errorKinds, kind)
			continue
		}
		t := taskqueue.NewPOSTTask(
			"/worker/purge_datastore",
			map[string][]string{"kind": {kind}})
		t.Name = fmt.Sprintf("purge-datastore-%s-%s", kind, time.Now().Format("2006-01-02T15-04-05"))
		_, err = taskqueue.Add(c, t, "default")
		if err != nil {
			c.Warningf("post err: %s", err)
			errorKinds = append(errorKinds, kind)
			continue
		}
		postedKinds = append(postedKinds, kind)
	}
	fmt.Fprintf(w, "Posted purge_datastore request: %s", strings.Join(postedKinds, ", "))
	if len(errorKinds) > 0 {
		fmt.Fprintf(w, "Excluded due to errors: %s", strings.Join(errorKinds, ", "))
	}
}

func handlePurgeDatastoreYMD(w http.ResponseWriter, r *http.Request) {
	c := appengine.NewContext(r)
	kind := r.FormValue("kind")
	begin, e1 := time.Parse("2006-01-02", r.FormValue("begin"))
	end, e2 := time.Parse("2006-01-02", r.FormValue("end"))
	if e1 != nil || e2 != nil {
		http.Error(w, "Invalid date format", http.StatusBadRequest)
		return
	}
	if begin.After(end) {
		http.Error(w, "Invalid date parameters", http.StatusBadRequest)
		return
	}
	postedKinds := make([]string, 0)
	for end.After(begin) {
		ymd := end.Format("20060102")
		ymdkind := fmt.Sprintf("%s-%s", kind, ymd)
		c.Infof("Posting purge_datastore for %s", ymdkind)

		t := taskqueue.NewPOSTTask(
			"/worker/purge_datastore",
			map[string][]string{"kind": {ymdkind}})
		t.Name = fmt.Sprintf("purge-datastore-%s-%s", ymdkind, time.Now().Format("2006-01-02T15-04-05"))
		_, err := taskqueue.Add(c, t, "default")
		if err != nil {
			c.Warningf("post err: %s", err)
			continue
		}
		postedKinds = append(postedKinds, ymdkind)
		end = end.Add(-24 * time.Hour)
	}
	fmt.Fprintf(w, "Posted purge_datastore requests: %s", strings.Join(postedKinds, ", "))
}

func purgeDatastore(w http.ResponseWriter, r *http.Request) {
	// Sadly we don't have metadata Kind queries in golang, so do this one by one.
	kind := r.FormValue("kind")
	c := appengine.NewContext(r)
	q := datastore.NewQuery(kind).Limit(300).KeysOnly()
	keys, err := q.GetAll(c, nil)
	if err == datastore.Done || len(keys) == 0 {
		// Done.
		c.Infof("Finished removing datastore: %s", kind)
		return
	}
	if err != nil {
		c.Errorf("Failed to query '%s': %s", kind, err.Error())
		return
	}
	err = datastore.DeleteMulti(c, keys)
	if err != nil {
		c.Errorf("Failed to purge '%s': %s", kind, err.Error())
		return
	}
	c.Infof("Removed %d entries in %s: scheduling next purge", len(keys), kind)
	t := taskqueue.NewPOSTTask(
		"/worker/purge_datastore",
		map[string][]string{"kind": {kind}})
	_, err = taskqueue.Add(c, t, "default")
	if err != nil {
		c.Errorf("post err: %s", err)
	}
}

func handleDeleteMemcache(w http.ResponseWriter, r *http.Request) {
	c := appengine.NewContext(r)
	key := r.FormValue("key")
	if key == "" {
		http.Error(w, "Needs to specify key", http.StatusBadRequest)
		return
	}
	err := memcache.Delete(c, key)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	fmt.Fprintf(w, "Deleted: %s", key)
}

func handleGetMemcacheCounter(w http.ResponseWriter, r *http.Request) {
	c := appengine.NewContext(r)
	key := r.FormValue("key")
	if key == "" {
		fmt.Fprintf(w, "Needs to specify key")
		return
	}
	counter := &Counter{key}
	v, err := counter.ReadOrError(c)
	fmt.Fprintf(w, "%s: %v (error: %v)", key, v, err)
}

func handleSetMemcacheCounter(w http.ResponseWriter, r *http.Request) {
	c := appengine.NewContext(r)
	key := r.FormValue("key")
	value := r.FormValue("value")
	if key == "" || value == "" {
		fmt.Fprintf(w, "Needs to specify key and value")
		return
	}
	counter := &Counter{key}
	i, _ := strconv.Atoi(value)
	if err := counter.Write(c, i); err != nil {
		fmt.Fprintf(w, "%v %v", key, err)
	}
}

func handleUpdateIssue(w http.ResponseWriter, r *http.Request) {
	c := appengine.NewContext(r)
	issueId := r.FormValue("issue")
	siteURL := r.FormValue("siteurl")

	q := datastore.NewQuery("Activity").Order("-Time").Filter("Issue =", fmt.Sprintf("%s/%s", siteURL, issueId)).Limit(1)
	var activities []Activity
	_, err := q.GetAll(c, &activities)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	for _, a := range activities {
		params := map[string][]string {
				"issue":{issueId}, "retry":{"0"}, "siteurl":{siteURL}}
			t := taskqueue.NewPOSTTask("/worker/poller_run_task",params)
		_, err := taskqueue.Add(c, t, "frequent")

		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			continue
		}
		fmt.Fprintf(w, "Scheduled polling %s\n", issueId)
		af := &ActivityFiles{}
		ar := &ActivityRecipients{}
		datastore.Delete(c, af.Key(c, &a))
		datastore.Delete(c, ar.Key(c, &a))
	}
	fmt.Fprintf(w, "DONE")
}

func handleIncomingMail(w http.ResponseWriter, r *http.Request) {
	c := appengine.NewContext(r)
	defer r.Body.Close()
	if msg, err := mail.ReadMessage(r.Body); err == nil {
		ProcessMail(c, msg)
	} else {
		c.Errorf("Error reading body: %v", err)
		return
	}
}
